﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Live;

namespace CuteRibs.D3Armory.LiveAsyncExtensions
{
	public static class LiveConnectClientAsyncExtension
	{
		public static Task<IDictionary<string, object>> GetAsyncTask(this LiveConnectClient client, string path)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.GetCompleted += OnOperationCompleted;
			client.GetAsync(path, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Get));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> PostAsyncTask(this LiveConnectClient client, string path, string body)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.PostCompleted += OnOperationCompleted;
			client.PostAsync(path, body, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Post));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> PostAsyncTask(this LiveConnectClient client, string path, IDictionary<string, object> body)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.PostCompleted += OnOperationCompleted;
			client.PostAsync(path, body, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Post));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> PutAsyncTask(this LiveConnectClient client, string path, string body)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.PutCompleted += OnOperationCompleted;
			client.PutAsync(path, body, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Put));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> PutAsyncTask(this LiveConnectClient client, string path, IDictionary<string, object> body)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.PutCompleted += OnOperationCompleted;
			client.PutAsync(path, body, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Put));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> DeleteAsyncTask(this LiveConnectClient client, string path)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.DeleteCompleted += OnOperationCompleted;
			client.DeleteAsync(path, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Delete));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> UploadAsyncTask(this LiveConnectClient client, string path, string fileName, Stream stream, OverwriteOption option = OverwriteOption.Overwrite, IProgress<LiveOperationProgress> progress = null)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.UploadProgressChanged += OnUploadProgressChanged;
			client.UploadCompleted += OnOperationCompleted;
			client.UploadAsync(path, fileName, stream, option, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Upload, progress));
			return tcs.Task;
		}

		public static Task<Stream> DownloadAsyncTask(this LiveConnectClient client, string path, IProgress<LiveOperationProgress> progress = null)
		{
			var tcs = new TaskCompletionSource<Stream>();
			client.DownloadProgressChanged += OnDownloadProgressChanged;
			client.DownloadCompleted += OnDownloadOperationCompleted;
			client.DownloadAsync(path, new OperationState<Stream>(tcs, client, ApiMethod.Download, progress));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> MoveAsyncTask(this LiveConnectClient client, string path, string destination)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.MoveCompleted += OnOperationCompleted;
			client.MoveAsync(path, destination, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Move));
			return tcs.Task;
		}

		public static Task<IDictionary<string, object>> CopyAsyncTask(this LiveConnectClient client, string path, string destination)
		{
			var tcs = new TaskCompletionSource<IDictionary<string, object>>();
			client.CopyCompleted += OnOperationCompleted;
			client.CopyAsync(path, destination, new OperationState<IDictionary<string, object>>(tcs, client, ApiMethod.Copy));
			return tcs.Task;
		}

		private static void OnOperationCompleted(object sender, LiveOperationCompletedEventArgs e)
		{
			var state = e.UserState as OperationState<IDictionary<string, object>>;

			if (state != null)
			{
				DetachOperation(state.Client, state.Method);

				if (e.Error == null)
				{
					state.TaskCompletionSource.TrySetResult(e.Result);
				}
				else
				{
					state.TaskCompletionSource.TrySetException(e.Error);
				}
			}
		}

		private static void OnUploadProgressChanged(object sender, LiveUploadProgressChangedEventArgs e)
		{
			var state = e.UserState as OperationState<IDictionary<string, object>>;

			if (state != null && state.Progress != null)
			{
				state.Progress.Report(new LiveOperationProgress(e.BytesSent, e.TotalBytesToSend));
			}
		}

		private static void OnDownloadProgressChanged(object sender, LiveDownloadProgressChangedEventArgs e)
		{
			var state = e.UserState as OperationState<Stream>;

			if (state != null && state.Progress != null)
			{
				state.Progress.Report(new LiveOperationProgress(e.BytesReceived, e.TotalBytesToReceive));
			}
		}

		private static void OnDownloadOperationCompleted(object sender, LiveDownloadCompletedEventArgs e)
		{
			var state = e.UserState as OperationState<Stream>;

			if (state != null)
			{
				DetachOperation(state.Client, state.Method);

				if (e.Error == null)
				{
					state.TaskCompletionSource.TrySetResult(e.Result);
				}
				else
				{
					state.TaskCompletionSource.TrySetException(e.Error);
				}
			}
		}

		private static void DetachOperation(LiveConnectClient client, ApiMethod method)
		{
			switch (method)
			{
				case ApiMethod.Get:
					client.GetCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Delete:
					client.DeleteCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Post:
					client.PostCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Put:
					client.PutCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Move:
					client.MoveCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Copy:
					client.CopyCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Upload:
					client.UploadProgressChanged -= OnUploadProgressChanged;
					client.UploadCompleted -= OnOperationCompleted;
					break;
				case ApiMethod.Download:
					client.DownloadProgressChanged -= OnDownloadProgressChanged;
					client.DownloadCompleted -= OnDownloadOperationCompleted;
					break;
				default:
					throw new InvalidOperationException("Un-supported method.");
			}
		}

		private class OperationState<TResult>
		{
			public TaskCompletionSource<TResult> TaskCompletionSource { get; private set; }
			public LiveConnectClient Client { get; private set; }
			public ApiMethod Method { get; private set; }
			public IProgress<LiveOperationProgress> Progress { get; set; }

			public OperationState(TaskCompletionSource<TResult> tcs, LiveConnectClient client, ApiMethod method, IProgress<LiveOperationProgress> progress = null)
			{
				this.TaskCompletionSource = tcs;
				this.Client = client;
				this.Method = method;
				this.Progress = progress;
			}
		}

		private enum ApiMethod
		{
			Get,
			Post,
			Put,
			Delete,
			Upload,
			Download,
			Move,
			Copy
		}
	}

	public struct LiveOperationProgress
	{
		public readonly long Bytes, TotalBytes;
		public readonly double Percentage;

		public LiveOperationProgress(long bytes, long totalBytes)
		{
			this.Bytes = bytes;
			this.TotalBytes = totalBytes;
			this.Percentage = (this.TotalBytes == 0) ? 0 : ((double)this.Bytes) / this.TotalBytes * 100;
		}
	}
}
